Let us set some global options for all code chunks in this document.

# Set seed for reproducibility
set.seed(1982) 
# Set global options for all code chunks
knitr::opts_chunk$set(
  # Disable messages printed by R code chunks
  message = TRUE,    
  # Disable warnings printed by R code chunks
  warning = TRUE,    
  # Show R code within code chunks in output
  echo = TRUE,        
  # Include both R code and its results in output
  include = TRUE,     
  # Evaluate R code chunks
  eval = TRUE,       
  # Enable caching of R code chunks for faster rendering
  cache = FALSE,      
  # Align figures in the center of the output
  fig.align = "center",
  # Enable retina display for high-resolution figures
  retina = 2,
  # Show errors in the output instead of stopping rendering
  error = TRUE,
  # Do not collapse code and output into a single block
  collapse = FALSE
)
# Start the figure counter
fig_count <- 0
# Define the captioner function
captioner <- function(caption) {
  fig_count <<- fig_count + 1
  paste0("Figure ", fig_count, ": ", caption)
}
# Define the function to truncate a number to two decimal places
truncate_to_two <- function(x) {
  floor(x * 100) / 100
}

m1table <- rSPDE:::m1table
m2table <- rSPDE:::m2table
m3table <- rSPDE:::m3table
m4table <- rSPDE:::m4table
# install.packages("INLA",repos=c(getOption("repos"),INLA="https://inla.r-inla-download.org/R/testing"), dep=TRUE)
# inla.upgrade(testing = TRUE)
# remotes::install_github("inlabru-org/inlabru", ref = "devel")
# remotes::install_github("davidbolin/rspde", ref = "devel")
# remotes::install_github("davidbolin/metricgraph", ref = "devel")
library(INLA)
## Loading required package: Matrix
## This is INLA_25.05.01 built 2025-05-01 18:43:33 UTC.
##  - See www.r-inla.org/contact-us for how to get help.
##  - List available models/likelihoods/etc with inla.list.models()
##  - Use inla.doc(<NAME>) to access documentation
##  - Consider upgrading R-INLA to testing[25.05.07] or stable[24.12.11].
library(inlabru)
## Loading required package: fmesher
library(rSPDE)
## This is rSPDE 2.5.1
## - See https://davidbolin.github.io/rSPDE for vignettes and manuals.
library(MetricGraph)
## This is MetricGraph 1.4.1
## - See https://davidbolin.github.io/MetricGraph for vignettes and manuals.
## 
## Attaching package: 'MetricGraph'
## The following object is masked from 'package:stats':
## 
##     filter
library(grateful)

library(plotly)
## Loading required package: ggplot2
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout

Utilitary functions

# For each m and beta, this function returns c_m/b_{m+1} and the roots of rb and rc
my.get.roots <- function(order, beta) {
  mt <- get(paste0("m", order, "table"))
  rb <- rep(0, order + 1)
  rc <- rep(0, order)
  if(order == 1) {
      rc = approx(mt$beta, mt[[paste0("rc")]], beta)$y
    } else {
      rc = sapply(1:order, function(i) {
        approx(mt$beta, mt[[paste0("rc.", i)]], beta)$y
      })
    }
    rb = sapply(1:(order+1), function(i) {
      approx(mt$beta, mt[[paste0("rb.", i)]], xout = beta)$y
    })
    factor = approx(mt$beta, mt$factor, xout = beta)$y
  return(list(rb = rb, rc = rc, factor = factor))
}

# Function the polynomial coefficients in increasing order like a+bx+cx^2+...
poly_from_roots <- function(roots) {
  
  coef <- 1
  for (r in roots) {coef <- convolve(coef, c(1, -r), type = "open")}
  return(coef)
}
# Function to compute the partial fraction parameters
compute_partial_fraction_param <- function(factor, pr_roots, pl_roots, cte) {
  
  pr_coef <- c(0, poly_from_roots(pr_roots)) 
  pl_coef <- poly_from_roots(pl_roots) 
  factor_pr_coef <- pr_coef
  pr_plus_pl_coef <- factor_pr_coef + cte/factor * pl_coef
  res <- gsignal::residue(factor_pr_coef, pr_plus_pl_coef)
  return(list(r = res$r, p = res$p, k = res$k)) 
}
# Function to compute the fractional operator
my.fractional.operators.frac <- function(L, beta, C, scale.factor, m = 1, time_step) {
  
  C <- Matrix::Diagonal(dim(C)[1], rowSums(C)) 
  Ci <- Matrix::Diagonal(dim(C)[1], 1 / rowSums(C)) 
  I <- Matrix::Diagonal(dim(C)[1])
  L <- L / scale.factor 
  LCi <- L %*% Ci
  if(beta == 1){
    return(list(Ci = Ci, C = C, LCi = LCi, L = L, m = m, beta = beta, LHS = C + time_step * L))
  } else {
    roots <- my.get.roots(m, beta)
    poles_rs_k <- compute_partial_fraction_param(roots$factor, roots$rc, roots$rb, time_step)

    partial.fraction.factors <- list()
    for (i in 1:(m+1)) {partial.fraction.factors[[i]] <- (LCi - poles_rs_k$p[i] * I)/poles_rs_k$r[i]}
    partial.fraction.factors[[m+2]] <- ifelse(is.null(poles_rs_k$k), 0, poles_rs_k$k) * I
    return(list(Ci = Ci, C = C, LCi = LCi, L = L, m = m, beta = beta, partial.fraction.factors = partial.fraction.factors))
  }
}
# Function to solve the iteration
my.solver.frac <- function(obj, v){
  beta <- obj$beta
  m <- obj$m
  C <- obj$C
  Ci <- obj$Ci
  if (beta == 1){
    return(solve(obj$LHS, v))
  } else {
    partial.fraction.factors <- obj$partial.fraction.factors
    output <- partial.fraction.factors[[m+2]] %*% v
    for (i in 1:(m+1)) {output <- output + solve(partial.fraction.factors[[i]], v)}
    return(Ci %*% output)
  }
}
# Function to build a tadpole graph and create a mesh
gets_graph_tadpole <- function(h){
  edge1 <- rbind(c(0,0),c(1,0))
  theta <- seq(from=-pi,to=pi,length.out = 10000)
  edge2 <- cbind(1+1/pi+cos(theta)/pi,sin(theta)/pi)
  edges = list(edge1, edge2)
  graph <- metric_graph$new(edges = edges)
  graph$set_manual_edge_lengths(edge_lengths = c(1,2))
  graph$build_mesh(h = h)
  return(graph)
}
# Function to compute the eigenfunctions 
tadpole.eig <- function(k,graph){
x1 <- c(0,graph$get_edge_lengths()[1]*graph$mesh$PtE[graph$mesh$PtE[,1]==1,2]) 
x2 <- c(0,graph$get_edge_lengths()[2]*graph$mesh$PtE[graph$mesh$PtE[,1]==2,2]) 

if(k==0){ 
  f.e1 <- rep(1,length(x1)) 
  f.e2 <- rep(1,length(x2)) 
  f1 = c(f.e1[1],f.e2[1],f.e1[-1], f.e2[-1]) 
  f = list(phi=f1/sqrt(3)) 
  
} else {
  f.e1 <- -2*sin(pi*k*1/2)*cos(pi*k*x1/2) 
  f.e2 <- sin(pi*k*x2/2)                  
  
  f1 = c(f.e1[1],f.e2[1],f.e1[-1], f.e2[-1]) 
  
  if((k %% 2)==1){ 
    f = list(phi=f1/sqrt(3)) 
  } else { 
    f.e1 <- (-1)^{k/2}*cos(pi*k*x1/2)
    f.e2 <- cos(pi*k*x2/2)
    f2 = c(f.e1[1],f.e2[1],f.e1[-1],f.e2[-1]) 
    f <- list(phi=f1,psi=f2/sqrt(3/2))
  }
}
return(f)
}
# Function to order the vertices for plotting
order_to_plot <- function(v, graph){
  edge_number <- graph$mesh$VtE[, 1]
  pos <- sum(edge_number == 1)+1
  return(c(v[1], v[3:pos], v[2], v[(pos+1):length(v)], v[2]))
}

We want to solve the fractional diffusion equation \[\begin{equation} \label{eq:maineq} \partial_t u+(\kappa^2-\Delta_\Gamma)^{\alpha/2} u=f \text { on } \Gamma \times(0, T), \quad u(0)=u_0 \text { on } \Gamma, \end{equation}\] where \(u\) satisfies the Kirchhoff vertex conditions \[\begin{equation} \label{eq:Kcond} \left\{\phi\in C(\Gamma)\;\Big|\; \forall v\in V: \sum_{e\in\mathcal{E}_v}\partial_e \phi(v)=0 \right\} \end{equation}\] The solution is given by \[\begin{equation} \label{eq:sol_reprentation} u(s,t) = \displaystyle\sum_{j\in\mathbb{N}}e^{-\lambda^{\alpha/2}_jt}\left(u_0, e_j\right)_{L_2(\Gamma)}e_j(s) + \int_0^t \displaystyle\sum_{j\in\mathbb{N}}e^{-\lambda^{\alpha/2}_j(t-r)}\left(f(\cdot, r), e_j\right)_{L_2(\Gamma)}e_j(s)dr. \end{equation}\]

If we choose \(w_j\) and \(v_j\) and take the initial condition and the right hand side funciton as

\[\begin{equation} u_0(s) = \sum_{j=0}^{N} w_j e_j(s) \text{ and so } \left(u_0, e_j\right)_{L_2(\Gamma)} = w_j, \end{equation}\] \[\begin{equation} \text{In matrix notation: } \quad\boldsymbol{U}_0 = \boldsymbol{E}^N_h\boldsymbol{c}, \quad \boldsymbol{E}^N_h = \left[e_0, e_1, \ldots, e_{N}\right], \quad \boldsymbol{c} = \left[w_0, w_1, \ldots, w_{N}\right]^\top, \end{equation}\] and \[\begin{equation} f(s,t) = \sum_{j=0}^{M} v_j e^{-\lambda^{\alpha/2}_jt} e_j(s) \text{ and so } \left(f(\cdot,r), e_j\right)_{L_2(\Gamma)} = v_j e^{-\lambda^{\alpha/2}_jr}, \end{equation}\] \[\begin{equation} \text{In matrix notation: } \quad\boldsymbol{f} = \boldsymbol{E}^M_h \boldsymbol{V}, \quad \boldsymbol{V}_{ji} = v_je^{-\lambda^{\alpha/2}_jt_i} \end{equation}\]

then the solution is given by \[\begin{align} u(s,t) &= \displaystyle\sum_{j=0}^{N}w_je^{-\lambda^{\alpha/2}_jt}e_j(s) + \int_0^t \displaystyle\sum_{j=0}^Me^{-\lambda^{\alpha/2}_j(t-r)}v_j e^{-\lambda^{\alpha/2}_jr}e_j(s)dr\\ &= \displaystyle\sum_{j=0}^{N}w_je^{-\lambda^{\alpha/2}_jt}e_j(s) + t \displaystyle\sum_{j=0}^Mv_j e^{-\lambda^{\alpha/2}_jt}e_j(s) \end{align}\]

\[\begin{equation} \text{In matrix notation: } \quad\boldsymbol{U} =\boldsymbol{E}^N_h \boldsymbol{W} + \boldsymbol{f}\boldsymbol{t}, \quad \boldsymbol{W}_{ji} = w_je^{-\lambda^{\alpha/2}_jt_i},\quad \boldsymbol{t} = \left[t_0, t_1, \ldots, t_{K}\right]^\top \end{equation}\]

h <- 0.01
graph <- gets_graph_tadpole(h = h)
## Starting graph creation...
## LongLat is set to FALSE
## Creating edges...
## Setting edge weights...
## Computing bounding box...
## Setting up edges
## Merging close vertices
## Total construction time: 0.17 secs
## Creating and updating vertices...
## Storing the initial graph...
## Computing the relative positions of the edges...
T_final <- 2
time_step <- 0.01
time_seq <- seq(0, T_final, by = time_step)
# Compute the FEM matrices
graph$compute_fem()
G <- graph$mesh$G
C <- graph$mesh$C
I <- Matrix::Diagonal(nrow(C))
x <- graph$mesh$V[, 1]
y <- graph$mesh$V[, 2]
weights <- graph$mesh$weights


kappa <- 1
alpha <- 0.5 # from 0.5 to 2
m = 1
beta <- alpha/2
L <- kappa^2*C + G



# Parameters to construct U_0
N_finite <- 4 # choose an even number
adjusted_N_finite <- N_finite + N_finite/2 + 1
EIGENVAL_ALPHA <- NULL
EIGENFUN <- NULL   
INDEX <- NULL
PHI_OR_PSI <- NULL
for (j in 0:N_finite) {
    lambda_j_alpha <- (kappa^2 + (j*pi/2)^2)^(alpha/2)
    e_j <- tadpole.eig(j,graph)$phi
    EIGENVAL_ALPHA <- c(EIGENVAL_ALPHA, lambda_j_alpha)         
    EIGENFUN <- cbind(EIGENFUN, e_j)   
    INDEX <- c(INDEX, j)
    PHI_OR_PSI <- c(PHI_OR_PSI, "phi")
    if (j>0 && (j %% 2 == 0)) {
      lambda_j_alpha <- (kappa^2 + (j*pi/2)^2)^(alpha/2)
      e_j <- tadpole.eig(j,graph)$psi
      EIGENVAL_ALPHA <- c(EIGENVAL_ALPHA, lambda_j_alpha)         
      EIGENFUN <- cbind(EIGENFUN, e_j)   
      INDEX <- c(INDEX, j)
      PHI_OR_PSI <- c(PHI_OR_PSI, "psi")
    }
}

# Building the initial condition as \sum coeff_j EIGENFUN_j
coeff <- 50*(1:length(EIGENVAL_ALPHA))^-1
coeff[-5] <- 0
U_0 <- EIGENFUN %*% coeff

U_true <- EIGENFUN %*% outer(1:length(EIGENVAL_ALPHA), 
                             1:length(time_seq), 
                             function(i, j) coeff[i] * exp(-EIGENVAL_ALPHA[i] * time_seq[j]))

c_k <- 10
what_eigenfunction_for_ff <- 7
ff <- function(t){
  return(c_k*EIGENFUN[,what_eigenfunction_for_ff]*exp(-t*EIGENVAL_ALPHA[what_eigenfunction_for_ff]))
}

FF_true <- matrix(NA, nrow = length(x), ncol = length(time_seq))
FF_sol_true <- matrix(NA, nrow = length(x), ncol = length(time_seq))
for (k in 1:length(time_seq)) {
  FF_true[, k] <- ff(time_seq[k]) # this is the right hand side function
  FF_sol_true[, k] <- time_seq[k]*FF_true[, k] # this is the second term in the solution
}

U_true <- U_true + FF_sol_true

graph_to_approx_int <- gets_graph_tadpole(h = 0.001)
## Starting graph creation...
## LongLat is set to FALSE
## Creating edges...
## Setting edge weights...
## Computing bounding box...
## Setting up edges
## Merging close vertices
## Total construction time: 0.16 secs
## Creating and updating vertices...
## Storing the initial graph...
## Computing the relative positions of the edges...
loc_finer <- graph_to_approx_int$get_mesh_locations()
A <- graph$fem_basis(loc_finer)
graph_to_approx_int$compute_fem()
C_finer <- graph_to_approx_int$mesh$C
EIGENFUN_FOR_FF <- tadpole.eig(INDEX[what_eigenfunction_for_ff], graph_to_approx_int)

if (PHI_OR_PSI[what_eigenfunction_for_ff] == "phi"){
  eigenfun_for_ff <- EIGENFUN_FOR_FF$phi
} else if (PHI_OR_PSI[what_eigenfunction_for_ff] == "psi"){
  eigenfun_for_ff <- EIGENFUN_FOR_FF$psi
}

int_basis_eigen <- as.vector(t(as.matrix(eigenfun_for_ff)) %*% C_finer %*% A) 
COEF <- c_k*exp(-time_seq*EIGENVAL_ALPHA[what_eigenfunction_for_ff])
FF_approx <- int_basis_eigen %*% t(COEF)

Solving it

{r}
coarse_h <- 0.1
coarse_graph <- gets_graph_tadpole(h = coarse_h)
coarse_A <- coarse_graph$fem_basis(graph$get_mesh_locations())
coarse_time_step <- 0.1
coarse_time_seq <- seq(0, T_final, by = coarse_time_step)
# Compute the FEM matrices
coarse_graph$compute_fem()
coarse_G <- coarse_graph$mesh$G
coarse_C <- coarse_graph$mesh$C
coarse_I <- Matrix::Diagonal(nrow(coarse_C))
coarse_x <- coarse_graph$mesh$V[, 1]
coarse_y <- coarse_graph$mesh$V[, 2]
coarse_edge_number <- coarse_graph$mesh$VtE[, 1]
coarse_pos <- sum(coarse_edge_number == 1)+1
coarse_order_to_plot <- function(v)return(c(v[1], v[3:coarse_pos], v[2], v[(coarse_pos+1):length(v)], v[2]))

coarse_weights <- coarse_graph$mesh$weights

coarse_L <- kappa^2*coarse_C + coarse_G

coarse_U_0 <- solve(t(coarse_A) %*% coarse_A, t(coarse_A) %*% U_0)

coarse_x <- coarse_order_to_plot(coarse_x)
coarse_y <- coarse_order_to_plot(coarse_y)

plot_ly(x = ~order_to_plot(x), y = ~order_to_plot(y), z = ~apply(U_0, 2,order_to_plot)[,1], type = 'scatter3d', mode = 'lines')
plot_ly(x = ~coarse_x, y = ~coarse_y, z = ~apply(coarse_U_0, 2, coarse_order_to_plot)[,1], type = 'scatter3d', mode = 'lines')
{r}
coarse_my_op_frac <- my.fractional.operators.frac(coarse_L, beta, coarse_C, scale.factor = kappa^2, m = m, coarse_time_step)

U_approx2 <- matrix(NA, nrow = nrow(coarse_C), ncol = length(coarse_time_seq))
U_approx2[, 1] <- U_0

# Time-stepping loop
for (k in 1:(length(coarse_time_seq) - 1)) {
  U_approx2[, k + 1] <- as.matrix(my.solver.frac(coarse_my_op_frac, coarse_my_op_frac$C %*% U_approx2[, k] + coarse_time_step * FF_approx[, k + 1]))
}
my_op_frac <- my.fractional.operators.frac(L, beta, C, scale.factor = kappa^2, m = m, time_step)

U_approx2 <- matrix(NA, nrow = nrow(C), ncol = length(time_seq))
U_approx2[, 1] <- U_0

# Time-stepping loop
for (k in 1:(length(time_seq) - 1)) {
  U_approx2[, k + 1] <- as.matrix(my.solver.frac(my_op_frac, my_op_frac$C %*% U_approx2[, k] + time_step * FF_approx[, k + 1]))
}

Plot

#U_approx2 <- U_approx1
U_approx1 <- U_approx2


mean_w <- function(v){return(mean(v*weights))}

max_error_at_each_time1 <- apply((U_true - U_approx1)^2, 2, mean)
max_error_at_each_time2 <- apply((U_true - U_approx2)^2, 2, mean)
max_error_between_both_approx <- apply((U_approx1 - U_approx2)^2, 2, mean)

error1 <- sqrt(time_step * sum(max_error_at_each_time1))
error2 <- sqrt(time_step * sum(max_error_at_each_time2))
errorb <- sqrt(time_step * sum(max_error_between_both_approx))


x <- order_to_plot(x, graph)
y <- order_to_plot(y, graph)
U_true <- apply(U_true, 2, order_to_plot, graph = graph)
U_approx1 <- apply(U_approx1, 2, order_to_plot, graph = graph)
U_approx2 <- apply(U_approx2, 2, order_to_plot, graph = graph)

# Create interactive plot
fig <- plot_ly()

# Add first line (max_error_at_each_time1)
fig <- fig %>% add_trace(
  x = ~time_seq, y = ~max_error_at_each_time1, type = 'scatter', mode = 'lines+markers',
  line = list(color = 'red', width = 2),
  marker = list(color = 'red', size = 4),
  name = paste0("Error  True and Approx 1: ", sprintf("%.3e", error1))
)

# Add second line (max_error_at_each_time2)
fig <- fig %>% add_trace(
  x = ~time_seq, y = ~max_error_at_each_time2, type = 'scatter', mode = 'lines+markers',
  line = list(color = 'blue', width = 2, dash = "dot"),
  marker = list(color = 'blue', size = 4),
  name = paste0("Error  True and Approx 2: ", sprintf("%.3e", error2))
)

# Add third line (max_error_between_both_approx)

fig <- fig %>% add_trace(
  x = ~time_seq, y = ~max_error_between_both_approx, type = 'scatter', mode = 'lines+markers',
  line = list(color = 'orange', width = 2),
  marker = list(color = 'orange', size = 4),
  name = paste0("Error Between Approximations: ", sprintf("%.3e", errorb))
)

# Layout
fig <- fig %>% layout(
  title = "Error at Each Time Step",
  xaxis = list(title = "Time"),
  yaxis = list(title = "Error"),
  legend = list(x = 0.1, y = 0.9)
)


plot_data <- data.frame(
  x = rep(x, times = ncol(U_true)),
  y = rep(y, times = ncol(U_true)),
  z_true = as.vector(U_true),
  z_approx1 = as.vector(U_approx1),
  z_approx2 = as.vector(U_approx2),
  frame = rep(time_seq, each = length(x))
)

# Compute axis limits
x_range <- range(x)
y_range <- range(y)
z_range <- range(c(U_true, U_approx1, U_approx2))

# Initial plot setup (first frame only)
p <- plot_ly(plot_data, frame = ~frame) %>%
  add_trace(
    x = ~x, y = ~y, z = ~z_true,
    type = "scatter3d", mode = "lines",
    name = "True",
    line = list(color = "green", width = 2)
  ) %>%
  add_trace(
    x = ~x, y = ~y, z = ~z_approx1,
    type = "scatter3d", mode = "lines",
    name = "Approx 1",
    line = list(color = "red", width = 2)
  ) %>%
  add_trace(
    x = ~x, y = ~y, z = ~z_approx2,
    type = "scatter3d", mode = "lines",
    name = "Approx 2",
    line = list(color = "blue", width = 2)
  ) %>%
  layout(
    scene = list(
      xaxis = list(title = "x", range = x_range),
      yaxis = list(title = "y", range = y_range),
      zaxis = list(title = "Value", range = z_range),
      aspectratio = list(x = 2.4, y = 1.2, z = 1.2),
           camera = list(
      eye = list(x = 1.5, y = 1.5, z = 1),  # Adjust the viewpoint
      center = list(x = 0, y = 0, z = 0))
    ),
    updatemenus = list(
      list(
        type = "buttons", showactive = FALSE,
        buttons = list(
          list(label = "Play", method = "animate",
               args = list(NULL, list(frame = list(duration = 100, redraw = TRUE), fromcurrent = TRUE))),
          list(label = "Pause", method = "animate",
               args = list(NULL, list(mode = "immediate", frame = list(duration = 0), redraw = FALSE)))
        )
      )
    ),
    title = "Time: 0"
  )

# Convert to plotly object with frame info
pb <- plotly_build(p)

# Inject custom titles into each frame
for (i in seq_along(pb$x$frames)) {
  t <- time_seq[i]
  err <- signif(max_error_between_both_approx[i], 4)
  pb$x$frames[[i]]$layout <- list(title = paste0("Time: ", t, " | Error: ", err))
}
## This is to plot the right hand side alone

FF_true <- apply(FF_true, 2, order_to_plot, graph = graph)
FF_sol_true <- apply(FF_sol_true, 2, order_to_plot, graph = graph)
FF_approx <- apply(FF_approx, 2, order_to_plot, graph = graph)

plot_data <- data.frame(
  x = rep(x, times = ncol(FF_true)),
  y = rep(y, times = ncol(FF_true)),
  ff_true = as.vector(FF_true),
  ff_sol_true = as.vector(FF_sol_true),
  ff_approx = as.vector(FF_approx),
  frame = rep(time_seq, each = length(x))
)

# Compute axis limits
x_range <- range(x)
y_range <- range(y)
z_range <- range(c(FF_true, FF_sol_true, FF_approx))

# Initial plot setup (first frame only)
p_ff <- plot_ly(plot_data, frame = ~frame) %>%
  add_trace(
    x = ~x, y = ~y, z = ~ff_true,
    type = "scatter3d", mode = "lines",
    name = "f(s,t)",
    line = list(color = "green", width = 2)
  ) %>%
  add_trace(
    x = ~x, y = ~y, z = ~ff_sol_true,
    type = "scatter3d", mode = "lines",
    name = "tf(s,t) = u(s,t)-SOL(u_0)",
    line = list(color = "red", width = 2)
  ) %>%
  add_trace(
    x = ~x, y = ~y, z = ~ff_approx,
    type = "scatter3d", mode = "lines",
    name = "F^k = (f^k, phi)",
    line = list(color = "blue", width = 2)
  ) %>%
  layout(
    scene = list(
      xaxis = list(title = "x", range = x_range),
      yaxis = list(title = "y", range = y_range),
      zaxis = list(title = "Value", range = z_range),
      aspectratio = list(x = 2.4, y = 1.2, z = 1.2),
           camera = list(
      eye = list(x = 1.5, y = 1.5, z = 1),  # Adjust the viewpoint
      center = list(x = 0, y = 0, z = 0))
    ),
    updatemenus = list(
      list(
        type = "buttons", showactive = FALSE,
        buttons = list(
          list(label = "Play", method = "animate",
               args = list(NULL, list(frame = list(duration = 100, redraw = TRUE), fromcurrent = TRUE))),
          list(label = "Pause", method = "animate",
               args = list(NULL, list(mode = "immediate", frame = list(duration = 0), redraw = FALSE)))
        )
      )
    ),
    title = "Time: 0"
  )

# Convert to plotly object with frame info
pb_ff <- plotly_build(p_ff)

# Inject custom titles into each frame
for (i in seq_along(pb_ff$x$frames)) {
  t <- time_seq[i]
  pb_ff$x$frames[[i]]$layout <- list(title = paste0("Time: ", t))
}
fig  # Display the plot
pb

Figure 1: Caption

pb_ff

Figure 2: Caption

LS0tCnRpdGxlOiAiU29sdmluZyBhIHBhcmFib2xpYyBlcXVhdGlvbiIKZGF0ZTogIkNyZWF0ZWQ6IDIwLTA0LTIwMjUuIExhc3QgbW9kaWZpZWQ6IGByIGZvcm1hdChTeXMudGltZSgpLCAnJWQtJW0tJVkuJylgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIG1hdGhqYXg6ICJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL21hdGhqYXhAMy9lczUvdGV4LW1tbC1jaHRtbC5qcyIKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRoZW1lOiBmbGF0bHkKICAgIGNvZGVfZm9sZGluZzogaGlkZSAjIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiIHRvIGhpZGUgY29kZSBhbmQgYWRkIGEgYnV0dG9uIHRvIHNob3cgaXQKICAgIGRmX3ByaW50OiBwYWdlZAogICAgIyB0b2M6IHRydWUKICAgICMgdG9jX2Zsb2F0OgogICAgIyAgIGNvbGxhcHNlZDogdHJ1ZQogICAgIyAgIHNtb290aF9zY3JvbGw6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCmFsd2F5c19hbGxvd19odG1sOiB0cnVlCmJpYmxpb2dyYXBoeTogCiAgLSByZWZlcmVuY2VzLmJpYgogIC0gZ3JhdGVmdWwtcmVmcy5iaWIKaGVhZGVyLWluY2x1ZGVzOgogIC0gXG5ld2NvbW1hbmR7XGFyfXtcbWF0aGJie1J9fQogIC0gXG5ld2NvbW1hbmR7XGxsYXZ9WzFde1xsZWZ0XHsjMVxyaWdodFx9fQogIC0gXG5ld2NvbW1hbmR7XHBhcmV9WzFde1xsZWZ0KCMxXHJpZ2h0KX0KICAtIFxuZXdjb21tYW5ke1xOY2FsfXtcbWF0aGNhbHtOfX0KICAtIFxuZXdjb21tYW5ke1xWY2FsfXtcbWF0aGNhbHtWfX0KICAtIFxuZXdjb21tYW5ke1xFY2FsfXtcbWF0aGNhbHtFfX0KICAtIFxuZXdjb21tYW5ke1xXY2FsfXtcbWF0aGNhbHtXfX0KLS0tCgpgYGB7ciB4YXJpbmdhbkV4dHJhLWNsaXBib2FyZCwgZWNobyA9IEZBTFNFfQpodG1sdG9vbHM6OnRhZ0xpc3QoCiAgeGFyaW5nYW5FeHRyYTo6dXNlX2NsaXBib2FyZCgKICAgIGJ1dHRvbl90ZXh0ID0gIjxpIGNsYXNzPVwiZmEtc29saWQgZmEtY2xpcGJvYXJkXCIgc3R5bGU9XCJjb2xvcjogIzAwMDA4QlwiPjwvaT4iLAogICAgc3VjY2Vzc190ZXh0ID0gIjxpIGNsYXNzPVwiZmEgZmEtY2hlY2tcIiBzdHlsZT1cImNvbG9yOiAjOTBCRTZEXCI+PC9pPiIsCiAgICBlcnJvcl90ZXh0ID0gIjxpIGNsYXNzPVwiZmEgZmEtdGltZXMtY2lyY2xlXCIgc3R5bGU9XCJjb2xvcjogI0Y5NDE0NFwiPjwvaT4iCiAgKSwKICBybWFya2Rvd246Omh0bWxfZGVwZW5kZW5jeV9mb250X2F3ZXNvbWUoKQopCmBgYAoKCmBgYHtjc3MsIGVjaG8gPSBGQUxTRX0KYm9keSAubWFpbi1jb250YWluZXIgewogIG1heC13aWR0aDogMTAwJSAhaW1wb3J0YW50OwogIHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7Cn0KYm9keSB7CiAgbWF4LXdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7Cn0KCmJvZHksIHRkIHsKICAgZm9udC1zaXplOiAxNnB4Owp9CmNvZGUucnsKICBmb250LXNpemU6IDE0cHg7Cn0KcHJlIHsKICBmb250LXNpemU6IDE0cHgKfQouY3VzdG9tLWJveCB7CiAgYmFja2dyb3VuZC1jb2xvcjogI2Y1ZjdmYTsgLyogTGlnaHQgZ3JleS1ibHVlIGJhY2tncm91bmQgKi8KICBib3JkZXItY29sb3I6ICNlMWU4ZWQ7IC8qIExpZ2h0IGJvcmRlciBjb2xvciAqLwogIGNvbG9yOiAjMmMzZTUwOyAvKiBEYXJrIHRleHQgY29sb3IgKi8KICBwYWRkaW5nOiAxNXB4OyAvKiBQYWRkaW5nIGluc2lkZSB0aGUgYm94ICovCiAgYm9yZGVyLXJhZGl1czogNXB4OyAvKiBSb3VuZGVkIGNvcm5lcnMgKi8KICBtYXJnaW4tYm90dG9tOiAyMHB4OyAvKiBTcGFjaW5nIGJlbG93IHRoZSBib3ggKi8KfQouY2FwdGlvbiB7CiAgbWFyZ2luOiBhdXRvOwogIHRleHQtYWxpZ246IGNlbnRlcjsKICBtYXJnaW4tYm90dG9tOiAyMHB4OyAvKiBTcGFjaW5nIGJlbG93IHRoZSBib3ggKi8KfQpgYGAKCgpMZXQgdXMgc2V0IHNvbWUgZ2xvYmFsIG9wdGlvbnMgZm9yIGFsbCBjb2RlIGNodW5rcyBpbiB0aGlzIGRvY3VtZW50LgoKCmBgYHtyfQojIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMTk4MikgCiMgU2V0IGdsb2JhbCBvcHRpb25zIGZvciBhbGwgY29kZSBjaHVua3MKa25pdHI6Om9wdHNfY2h1bmskc2V0KAogICMgRGlzYWJsZSBtZXNzYWdlcyBwcmludGVkIGJ5IFIgY29kZSBjaHVua3MKICBtZXNzYWdlID0gVFJVRSwgICAgCiAgIyBEaXNhYmxlIHdhcm5pbmdzIHByaW50ZWQgYnkgUiBjb2RlIGNodW5rcwogIHdhcm5pbmcgPSBUUlVFLCAgICAKICAjIFNob3cgUiBjb2RlIHdpdGhpbiBjb2RlIGNodW5rcyBpbiBvdXRwdXQKICBlY2hvID0gVFJVRSwgICAgICAgIAogICMgSW5jbHVkZSBib3RoIFIgY29kZSBhbmQgaXRzIHJlc3VsdHMgaW4gb3V0cHV0CiAgaW5jbHVkZSA9IFRSVUUsICAgICAKICAjIEV2YWx1YXRlIFIgY29kZSBjaHVua3MKICBldmFsID0gVFJVRSwgICAgICAgCiAgIyBFbmFibGUgY2FjaGluZyBvZiBSIGNvZGUgY2h1bmtzIGZvciBmYXN0ZXIgcmVuZGVyaW5nCiAgY2FjaGUgPSBGQUxTRSwgICAgICAKICAjIEFsaWduIGZpZ3VyZXMgaW4gdGhlIGNlbnRlciBvZiB0aGUgb3V0cHV0CiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgIyBFbmFibGUgcmV0aW5hIGRpc3BsYXkgZm9yIGhpZ2gtcmVzb2x1dGlvbiBmaWd1cmVzCiAgcmV0aW5hID0gMiwKICAjIFNob3cgZXJyb3JzIGluIHRoZSBvdXRwdXQgaW5zdGVhZCBvZiBzdG9wcGluZyByZW5kZXJpbmcKICBlcnJvciA9IFRSVUUsCiAgIyBEbyBub3QgY29sbGFwc2UgY29kZSBhbmQgb3V0cHV0IGludG8gYSBzaW5nbGUgYmxvY2sKICBjb2xsYXBzZSA9IEZBTFNFCikKIyBTdGFydCB0aGUgZmlndXJlIGNvdW50ZXIKZmlnX2NvdW50IDwtIDAKIyBEZWZpbmUgdGhlIGNhcHRpb25lciBmdW5jdGlvbgpjYXB0aW9uZXIgPC0gZnVuY3Rpb24oY2FwdGlvbikgewogIGZpZ19jb3VudCA8PC0gZmlnX2NvdW50ICsgMQogIHBhc3RlMCgiRmlndXJlICIsIGZpZ19jb3VudCwgIjogIiwgY2FwdGlvbikKfQojIERlZmluZSB0aGUgZnVuY3Rpb24gdG8gdHJ1bmNhdGUgYSBudW1iZXIgdG8gdHdvIGRlY2ltYWwgcGxhY2VzCnRydW5jYXRlX3RvX3R3byA8LSBmdW5jdGlvbih4KSB7CiAgZmxvb3IoeCAqIDEwMCkgLyAxMDAKfQoKbTF0YWJsZSA8LSByU1BERTo6Om0xdGFibGUKbTJ0YWJsZSA8LSByU1BERTo6Om0ydGFibGUKbTN0YWJsZSA8LSByU1BERTo6Om0zdGFibGUKbTR0YWJsZSA8LSByU1BERTo6Om00dGFibGUKYGBgCgoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiSU5MQSIscmVwb3M9YyhnZXRPcHRpb24oInJlcG9zIiksSU5MQT0iaHR0cHM6Ly9pbmxhLnItaW5sYS1kb3dubG9hZC5vcmcvUi90ZXN0aW5nIiksIGRlcD1UUlVFKQojIGlubGEudXBncmFkZSh0ZXN0aW5nID0gVFJVRSkKIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiaW5sYWJydS1vcmcvaW5sYWJydSIsIHJlZiA9ICJkZXZlbCIpCiMgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImRhdmlkYm9saW4vcnNwZGUiLCByZWYgPSAiZGV2ZWwiKQojIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJkYXZpZGJvbGluL21ldHJpY2dyYXBoIiwgcmVmID0gImRldmVsIikKbGlicmFyeShJTkxBKQpsaWJyYXJ5KGlubGFicnUpCmxpYnJhcnkoclNQREUpCmxpYnJhcnkoTWV0cmljR3JhcGgpCmxpYnJhcnkoZ3JhdGVmdWwpCgpsaWJyYXJ5KHBsb3RseSkKYGBgCgoKIyBVdGlsaXRhcnkgZnVuY3Rpb25zCgpgYGB7cn0KIyBGb3IgZWFjaCBtIGFuZCBiZXRhLCB0aGlzIGZ1bmN0aW9uIHJldHVybnMgY19tL2Jfe20rMX0gYW5kIHRoZSByb290cyBvZiByYiBhbmQgcmMKbXkuZ2V0LnJvb3RzIDwtIGZ1bmN0aW9uKG9yZGVyLCBiZXRhKSB7CiAgbXQgPC0gZ2V0KHBhc3RlMCgibSIsIG9yZGVyLCAidGFibGUiKSkKICByYiA8LSByZXAoMCwgb3JkZXIgKyAxKQogIHJjIDwtIHJlcCgwLCBvcmRlcikKICBpZihvcmRlciA9PSAxKSB7CiAgICAgIHJjID0gYXBwcm94KG10JGJldGEsIG10W1twYXN0ZTAoInJjIildXSwgYmV0YSkkeQogICAgfSBlbHNlIHsKICAgICAgcmMgPSBzYXBwbHkoMTpvcmRlciwgZnVuY3Rpb24oaSkgewogICAgICAgIGFwcHJveChtdCRiZXRhLCBtdFtbcGFzdGUwKCJyYy4iLCBpKV1dLCBiZXRhKSR5CiAgICAgIH0pCiAgICB9CiAgICByYiA9IHNhcHBseSgxOihvcmRlcisxKSwgZnVuY3Rpb24oaSkgewogICAgICBhcHByb3gobXQkYmV0YSwgbXRbW3Bhc3RlMCgicmIuIiwgaSldXSwgeG91dCA9IGJldGEpJHkKICAgIH0pCiAgICBmYWN0b3IgPSBhcHByb3gobXQkYmV0YSwgbXQkZmFjdG9yLCB4b3V0ID0gYmV0YSkkeQogIHJldHVybihsaXN0KHJiID0gcmIsIHJjID0gcmMsIGZhY3RvciA9IGZhY3RvcikpCn0KCiMgRnVuY3Rpb24gdGhlIHBvbHlub21pYWwgY29lZmZpY2llbnRzIGluIGluY3JlYXNpbmcgb3JkZXIgbGlrZSBhK2J4K2N4XjIrLi4uCnBvbHlfZnJvbV9yb290cyA8LSBmdW5jdGlvbihyb290cykgewogIAogIGNvZWYgPC0gMQogIGZvciAociBpbiByb290cykge2NvZWYgPC0gY29udm9sdmUoY29lZiwgYygxLCAtciksIHR5cGUgPSAib3BlbiIpfQogIHJldHVybihjb2VmKQp9CiMgRnVuY3Rpb24gdG8gY29tcHV0ZSB0aGUgcGFydGlhbCBmcmFjdGlvbiBwYXJhbWV0ZXJzCmNvbXB1dGVfcGFydGlhbF9mcmFjdGlvbl9wYXJhbSA8LSBmdW5jdGlvbihmYWN0b3IsIHByX3Jvb3RzLCBwbF9yb290cywgY3RlKSB7CiAgCiAgcHJfY29lZiA8LSBjKDAsIHBvbHlfZnJvbV9yb290cyhwcl9yb290cykpIAogIHBsX2NvZWYgPC0gcG9seV9mcm9tX3Jvb3RzKHBsX3Jvb3RzKSAKICBmYWN0b3JfcHJfY29lZiA8LSBwcl9jb2VmCiAgcHJfcGx1c19wbF9jb2VmIDwtIGZhY3Rvcl9wcl9jb2VmICsgY3RlL2ZhY3RvciAqIHBsX2NvZWYKICByZXMgPC0gZ3NpZ25hbDo6cmVzaWR1ZShmYWN0b3JfcHJfY29lZiwgcHJfcGx1c19wbF9jb2VmKQogIHJldHVybihsaXN0KHIgPSByZXMkciwgcCA9IHJlcyRwLCBrID0gcmVzJGspKSAKfQojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIGZyYWN0aW9uYWwgb3BlcmF0b3IKbXkuZnJhY3Rpb25hbC5vcGVyYXRvcnMuZnJhYyA8LSBmdW5jdGlvbihMLCBiZXRhLCBDLCBzY2FsZS5mYWN0b3IsIG0gPSAxLCB0aW1lX3N0ZXApIHsKICAKICBDIDwtIE1hdHJpeDo6RGlhZ29uYWwoZGltKEMpWzFdLCByb3dTdW1zKEMpKSAKICBDaSA8LSBNYXRyaXg6OkRpYWdvbmFsKGRpbShDKVsxXSwgMSAvIHJvd1N1bXMoQykpIAogIEkgPC0gTWF0cml4OjpEaWFnb25hbChkaW0oQylbMV0pCiAgTCA8LSBMIC8gc2NhbGUuZmFjdG9yIAogIExDaSA8LSBMICUqJSBDaQogIGlmKGJldGEgPT0gMSl7CiAgICByZXR1cm4obGlzdChDaSA9IENpLCBDID0gQywgTENpID0gTENpLCBMID0gTCwgbSA9IG0sIGJldGEgPSBiZXRhLCBMSFMgPSBDICsgdGltZV9zdGVwICogTCkpCiAgfSBlbHNlIHsKICAgIHJvb3RzIDwtIG15LmdldC5yb290cyhtLCBiZXRhKQogICAgcG9sZXNfcnNfayA8LSBjb21wdXRlX3BhcnRpYWxfZnJhY3Rpb25fcGFyYW0ocm9vdHMkZmFjdG9yLCByb290cyRyYywgcm9vdHMkcmIsIHRpbWVfc3RlcCkKCiAgICBwYXJ0aWFsLmZyYWN0aW9uLmZhY3RvcnMgPC0gbGlzdCgpCiAgICBmb3IgKGkgaW4gMToobSsxKSkge3BhcnRpYWwuZnJhY3Rpb24uZmFjdG9yc1tbaV1dIDwtIChMQ2kgLSBwb2xlc19yc19rJHBbaV0gKiBJKS9wb2xlc19yc19rJHJbaV19CiAgICBwYXJ0aWFsLmZyYWN0aW9uLmZhY3RvcnNbW20rMl1dIDwtIGlmZWxzZShpcy5udWxsKHBvbGVzX3JzX2skayksIDAsIHBvbGVzX3JzX2skaykgKiBJCiAgICByZXR1cm4obGlzdChDaSA9IENpLCBDID0gQywgTENpID0gTENpLCBMID0gTCwgbSA9IG0sIGJldGEgPSBiZXRhLCBwYXJ0aWFsLmZyYWN0aW9uLmZhY3RvcnMgPSBwYXJ0aWFsLmZyYWN0aW9uLmZhY3RvcnMpKQogIH0KfQojIEZ1bmN0aW9uIHRvIHNvbHZlIHRoZSBpdGVyYXRpb24KbXkuc29sdmVyLmZyYWMgPC0gZnVuY3Rpb24ob2JqLCB2KXsKICBiZXRhIDwtIG9iaiRiZXRhCiAgbSA8LSBvYmokbQogIEMgPC0gb2JqJEMKICBDaSA8LSBvYmokQ2kKICBpZiAoYmV0YSA9PSAxKXsKICAgIHJldHVybihzb2x2ZShvYmokTEhTLCB2KSkKICB9IGVsc2UgewogICAgcGFydGlhbC5mcmFjdGlvbi5mYWN0b3JzIDwtIG9iaiRwYXJ0aWFsLmZyYWN0aW9uLmZhY3RvcnMKICAgIG91dHB1dCA8LSBwYXJ0aWFsLmZyYWN0aW9uLmZhY3RvcnNbW20rMl1dICUqJSB2CiAgICBmb3IgKGkgaW4gMToobSsxKSkge291dHB1dCA8LSBvdXRwdXQgKyBzb2x2ZShwYXJ0aWFsLmZyYWN0aW9uLmZhY3RvcnNbW2ldXSwgdil9CiAgICByZXR1cm4oQ2kgJSolIG91dHB1dCkKICB9Cn0KIyBGdW5jdGlvbiB0byBidWlsZCBhIHRhZHBvbGUgZ3JhcGggYW5kIGNyZWF0ZSBhIG1lc2gKZ2V0c19ncmFwaF90YWRwb2xlIDwtIGZ1bmN0aW9uKGgpewogIGVkZ2UxIDwtIHJiaW5kKGMoMCwwKSxjKDEsMCkpCiAgdGhldGEgPC0gc2VxKGZyb209LXBpLHRvPXBpLGxlbmd0aC5vdXQgPSAxMDAwMCkKICBlZGdlMiA8LSBjYmluZCgxKzEvcGkrY29zKHRoZXRhKS9waSxzaW4odGhldGEpL3BpKQogIGVkZ2VzID0gbGlzdChlZGdlMSwgZWRnZTIpCiAgZ3JhcGggPC0gbWV0cmljX2dyYXBoJG5ldyhlZGdlcyA9IGVkZ2VzKQogIGdyYXBoJHNldF9tYW51YWxfZWRnZV9sZW5ndGhzKGVkZ2VfbGVuZ3RocyA9IGMoMSwyKSkKICBncmFwaCRidWlsZF9tZXNoKGggPSBoKQogIHJldHVybihncmFwaCkKfQojIEZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIGVpZ2VuZnVuY3Rpb25zIAp0YWRwb2xlLmVpZyA8LSBmdW5jdGlvbihrLGdyYXBoKXsKeDEgPC0gYygwLGdyYXBoJGdldF9lZGdlX2xlbmd0aHMoKVsxXSpncmFwaCRtZXNoJFB0RVtncmFwaCRtZXNoJFB0RVssMV09PTEsMl0pIAp4MiA8LSBjKDAsZ3JhcGgkZ2V0X2VkZ2VfbGVuZ3RocygpWzJdKmdyYXBoJG1lc2gkUHRFW2dyYXBoJG1lc2gkUHRFWywxXT09MiwyXSkgCgppZihrPT0wKXsgCiAgZi5lMSA8LSByZXAoMSxsZW5ndGgoeDEpKSAKICBmLmUyIDwtIHJlcCgxLGxlbmd0aCh4MikpIAogIGYxID0gYyhmLmUxWzFdLGYuZTJbMV0sZi5lMVstMV0sIGYuZTJbLTFdKSAKICBmID0gbGlzdChwaGk9ZjEvc3FydCgzKSkgCiAgCn0gZWxzZSB7CiAgZi5lMSA8LSAtMipzaW4ocGkqayoxLzIpKmNvcyhwaSprKngxLzIpIAogIGYuZTIgPC0gc2luKHBpKmsqeDIvMikgICAgICAgICAgICAgICAgICAKICAKICBmMSA9IGMoZi5lMVsxXSxmLmUyWzFdLGYuZTFbLTFdLCBmLmUyWy0xXSkgCiAgCiAgaWYoKGsgJSUgMik9PTEpeyAKICAgIGYgPSBsaXN0KHBoaT1mMS9zcXJ0KDMpKSAKICB9IGVsc2UgeyAKICAgIGYuZTEgPC0gKC0xKV57ay8yfSpjb3MocGkqayp4MS8yKQogICAgZi5lMiA8LSBjb3MocGkqayp4Mi8yKQogICAgZjIgPSBjKGYuZTFbMV0sZi5lMlsxXSxmLmUxWy0xXSxmLmUyWy0xXSkgCiAgICBmIDwtIGxpc3QocGhpPWYxLHBzaT1mMi9zcXJ0KDMvMikpCiAgfQp9CnJldHVybihmKQp9CiMgRnVuY3Rpb24gdG8gb3JkZXIgdGhlIHZlcnRpY2VzIGZvciBwbG90dGluZwpvcmRlcl90b19wbG90IDwtIGZ1bmN0aW9uKHYsIGdyYXBoKXsKICBlZGdlX251bWJlciA8LSBncmFwaCRtZXNoJFZ0RVssIDFdCiAgcG9zIDwtIHN1bShlZGdlX251bWJlciA9PSAxKSsxCiAgcmV0dXJuKGModlsxXSwgdlszOnBvc10sIHZbMl0sIHZbKHBvcysxKTpsZW5ndGgodildLCB2WzJdKSkKfQpgYGAKCgpXZSB3YW50IHRvIHNvbHZlIHRoZSBmcmFjdGlvbmFsIGRpZmZ1c2lvbiBlcXVhdGlvbgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTptYWluZXF9CiAgICBccGFydGlhbF90IHUrKFxrYXBwYV4yLVxEZWx0YV9cR2FtbWEpXntcYWxwaGEvMn0gdT1mIFx0ZXh0IHsgb24gfSBcR2FtbWEgXHRpbWVzKDAsIFQpLCBccXVhZCB1KDApPXVfMCBcdGV4dCB7IG9uIH0gXEdhbW1hLApcZW5ke2VxdWF0aW9ufQp3aGVyZSAkdSQgc2F0aXNmaWVzIHRoZSBLaXJjaGhvZmYgdmVydGV4IGNvbmRpdGlvbnMKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6S2NvbmR9CiAgICBcbGVmdFx7XHBoaVxpbiBDKFxHYW1tYSlcO1xCaWd8XDsgXGZvcmFsbCB2XGluIFY6IFxzdW1fe2VcaW5cbWF0aGNhbHtFfV92fVxwYXJ0aWFsX2UgXHBoaSh2KT0wIFxyaWdodFx9ClxlbmR7ZXF1YXRpb259ClRoZSBzb2x1dGlvbiBpcyBnaXZlbiBieQpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpzb2xfcmVwcmVudGF0aW9ufQogICAgICAgIHUocyx0KSA9IFxkaXNwbGF5c3R5bGVcc3VtX3tqXGluXG1hdGhiYntOfX1lXnstXGxhbWJkYV57XGFscGhhLzJ9X2p0fVxsZWZ0KHVfMCwgZV9qXHJpZ2h0KV97TF8yKFxHYW1tYSl9ZV9qKHMpICsgXGludF8wXnQgXGRpc3BsYXlzdHlsZVxzdW1fe2pcaW5cbWF0aGJie059fWVeey1cbGFtYmRhXntcYWxwaGEvMn1faih0LXIpfVxsZWZ0KGYoXGNkb3QsIHIpLCBlX2pccmlnaHQpX3tMXzIoXEdhbW1hKX1lX2oocylkci4KXGVuZHtlcXVhdGlvbn0KCklmIHdlIGNob29zZSAkd19qJCBhbmQgJHZfaiQgYW5kIHRha2UgdGhlIGluaXRpYWwgY29uZGl0aW9uIGFuZCB0aGUgcmlnaHQgaGFuZCBzaWRlIGZ1bmNpdG9uIGFzIAoKXGJlZ2lue2VxdWF0aW9ufQogICAgdV8wKHMpID0gXHN1bV97aj0wfV57Tn0gd19qIGVfaihzKSBcdGV4dHsgYW5kIHNvIH0gXGxlZnQodV8wLCBlX2pccmlnaHQpX3tMXzIoXEdhbW1hKX0gPSB3X2osClxlbmR7ZXF1YXRpb259ClxiZWdpbntlcXVhdGlvbn0KICAgXHRleHR7SW4gbWF0cml4IG5vdGF0aW9uOiB9IFxxdWFkXGJvbGRzeW1ib2x7VX1fMCA9IFxib2xkc3ltYm9se0V9Xk5faFxib2xkc3ltYm9se2N9LCBccXVhZCAgXGJvbGRzeW1ib2x7RX1eTl9oID0gXGxlZnRbZV8wLCBlXzEsIFxsZG90cywgZV97Tn1ccmlnaHRdLCBccXVhZCBcYm9sZHN5bWJvbHtjfSA9IFxsZWZ0W3dfMCwgd18xLCBcbGRvdHMsIHdfe059XHJpZ2h0XV5cdG9wLApcZW5ke2VxdWF0aW9ufQphbmQKXGJlZ2lue2VxdWF0aW9ufQogICAgZihzLHQpID0gXHN1bV97aj0wfV57TX0gdl9qIGVeey1cbGFtYmRhXntcYWxwaGEvMn1fanR9IGVfaihzKSBcdGV4dHsgYW5kIHNvIH0gXGxlZnQoZihcY2RvdCxyKSwgZV9qXHJpZ2h0KV97TF8yKFxHYW1tYSl9ID0gdl9qIGVeey1cbGFtYmRhXntcYWxwaGEvMn1fanJ9LApcZW5ke2VxdWF0aW9ufQpcYmVnaW57ZXF1YXRpb259CiAgIFx0ZXh0e0luIG1hdHJpeCBub3RhdGlvbjogfSBccXVhZFxib2xkc3ltYm9se2Z9ID0gXGJvbGRzeW1ib2x7RX1eTV9oIFxib2xkc3ltYm9se1Z9LCBccXVhZCBcYm9sZHN5bWJvbHtWfV97aml9ID0gdl9qZV57LVxsYW1iZGFee1xhbHBoYS8yfV9qdF9pfQpcZW5ke2VxdWF0aW9ufQoKdGhlbiB0aGUgc29sdXRpb24gaXMgZ2l2ZW4gYnkKXGJlZ2lue2FsaWdufQogICAgICAgIHUocyx0KSAmPSBcZGlzcGxheXN0eWxlXHN1bV97aj0wfV57Tn13X2plXnstXGxhbWJkYV57XGFscGhhLzJ9X2p0fWVfaihzKSArIFxpbnRfMF50IFxkaXNwbGF5c3R5bGVcc3VtX3tqPTB9Xk1lXnstXGxhbWJkYV57XGFscGhhLzJ9X2oodC1yKX12X2ogZV57LVxsYW1iZGFee1xhbHBoYS8yfV9qcn1lX2oocylkclxcCiAgICAgICAgJj0gXGRpc3BsYXlzdHlsZVxzdW1fe2o9MH1ee059d19qZV57LVxsYW1iZGFee1xhbHBoYS8yfV9qdH1lX2oocykgKyB0IFxkaXNwbGF5c3R5bGVcc3VtX3tqPTB9Xk12X2ogZV57LVxsYW1iZGFee1xhbHBoYS8yfV9qdH1lX2oocykKXGVuZHthbGlnbn0KClxiZWdpbntlcXVhdGlvbn0KICAgXHRleHR7SW4gbWF0cml4IG5vdGF0aW9uOiB9IFxxdWFkXGJvbGRzeW1ib2x7VX0gPVxib2xkc3ltYm9se0V9Xk5faCBcYm9sZHN5bWJvbHtXfSArIFxib2xkc3ltYm9se2Z9XGJvbGRzeW1ib2x7dH0sIFxxdWFkIFxib2xkc3ltYm9se1d9X3tqaX0gPSB3X2plXnstXGxhbWJkYV57XGFscGhhLzJ9X2p0X2l9LFxxdWFkIFxib2xkc3ltYm9se3R9ID0gXGxlZnRbdF8wLCB0XzEsIFxsZG90cywgdF97S31ccmlnaHRdXlx0b3AKXGVuZHtlcXVhdGlvbn0KCgpgYGB7cn0KaCA8LSAwLjAxCmdyYXBoIDwtIGdldHNfZ3JhcGhfdGFkcG9sZShoID0gaCkKVF9maW5hbCA8LSAyCnRpbWVfc3RlcCA8LSAwLjAxCnRpbWVfc2VxIDwtIHNlcSgwLCBUX2ZpbmFsLCBieSA9IHRpbWVfc3RlcCkKIyBDb21wdXRlIHRoZSBGRU0gbWF0cmljZXMKZ3JhcGgkY29tcHV0ZV9mZW0oKQpHIDwtIGdyYXBoJG1lc2gkRwpDIDwtIGdyYXBoJG1lc2gkQwpJIDwtIE1hdHJpeDo6RGlhZ29uYWwobnJvdyhDKSkKeCA8LSBncmFwaCRtZXNoJFZbLCAxXQp5IDwtIGdyYXBoJG1lc2gkVlssIDJdCndlaWdodHMgPC0gZ3JhcGgkbWVzaCR3ZWlnaHRzCgoKa2FwcGEgPC0gMQphbHBoYSA8LSAwLjUgIyBmcm9tIDAuNSB0byAyCm0gPSAxCmJldGEgPC0gYWxwaGEvMgpMIDwtIGthcHBhXjIqQyArIEcKCgoKIyBQYXJhbWV0ZXJzIHRvIGNvbnN0cnVjdCBVXzAKTl9maW5pdGUgPC0gNCAjIGNob29zZSBhbiBldmVuIG51bWJlcgphZGp1c3RlZF9OX2Zpbml0ZSA8LSBOX2Zpbml0ZSArIE5fZmluaXRlLzIgKyAxCkVJR0VOVkFMX0FMUEhBIDwtIE5VTEwKRUlHRU5GVU4gPC0gTlVMTCAgIApJTkRFWCA8LSBOVUxMClBISV9PUl9QU0kgPC0gTlVMTApmb3IgKGogaW4gMDpOX2Zpbml0ZSkgewogICAgbGFtYmRhX2pfYWxwaGEgPC0gKGthcHBhXjIgKyAoaipwaS8yKV4yKV4oYWxwaGEvMikKICAgIGVfaiA8LSB0YWRwb2xlLmVpZyhqLGdyYXBoKSRwaGkKICAgIEVJR0VOVkFMX0FMUEhBIDwtIGMoRUlHRU5WQUxfQUxQSEEsIGxhbWJkYV9qX2FscGhhKSAgICAgICAgIAogICAgRUlHRU5GVU4gPC0gY2JpbmQoRUlHRU5GVU4sIGVfaikgICAKICAgIElOREVYIDwtIGMoSU5ERVgsIGopCiAgICBQSElfT1JfUFNJIDwtIGMoUEhJX09SX1BTSSwgInBoaSIpCiAgICBpZiAoaj4wICYmIChqICUlIDIgPT0gMCkpIHsKICAgICAgbGFtYmRhX2pfYWxwaGEgPC0gKGthcHBhXjIgKyAoaipwaS8yKV4yKV4oYWxwaGEvMikKICAgICAgZV9qIDwtIHRhZHBvbGUuZWlnKGosZ3JhcGgpJHBzaQogICAgICBFSUdFTlZBTF9BTFBIQSA8LSBjKEVJR0VOVkFMX0FMUEhBLCBsYW1iZGFfal9hbHBoYSkgICAgICAgICAKICAgICAgRUlHRU5GVU4gPC0gY2JpbmQoRUlHRU5GVU4sIGVfaikgICAKICAgICAgSU5ERVggPC0gYyhJTkRFWCwgaikKICAgICAgUEhJX09SX1BTSSA8LSBjKFBISV9PUl9QU0ksICJwc2kiKQogICAgfQp9CgojIEJ1aWxkaW5nIHRoZSBpbml0aWFsIGNvbmRpdGlvbiBhcyBcc3VtIGNvZWZmX2ogRUlHRU5GVU5fagpjb2VmZiA8LSA1MCooMTpsZW5ndGgoRUlHRU5WQUxfQUxQSEEpKV4tMQpjb2VmZlstNV0gPC0gMApVXzAgPC0gRUlHRU5GVU4gJSolIGNvZWZmCgpVX3RydWUgPC0gRUlHRU5GVU4gJSolIG91dGVyKDE6bGVuZ3RoKEVJR0VOVkFMX0FMUEhBKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTpsZW5ndGgodGltZV9zZXEpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbihpLCBqKSBjb2VmZltpXSAqIGV4cCgtRUlHRU5WQUxfQUxQSEFbaV0gKiB0aW1lX3NlcVtqXSkpCgpjX2sgPC0gMTAKd2hhdF9laWdlbmZ1bmN0aW9uX2Zvcl9mZiA8LSA3CmZmIDwtIGZ1bmN0aW9uKHQpewogIHJldHVybihjX2sqRUlHRU5GVU5bLHdoYXRfZWlnZW5mdW5jdGlvbl9mb3JfZmZdKmV4cCgtdCpFSUdFTlZBTF9BTFBIQVt3aGF0X2VpZ2VuZnVuY3Rpb25fZm9yX2ZmXSkpCn0KCkZGX3RydWUgPC0gbWF0cml4KE5BLCBucm93ID0gbGVuZ3RoKHgpLCBuY29sID0gbGVuZ3RoKHRpbWVfc2VxKSkKRkZfc29sX3RydWUgPC0gbWF0cml4KE5BLCBucm93ID0gbGVuZ3RoKHgpLCBuY29sID0gbGVuZ3RoKHRpbWVfc2VxKSkKZm9yIChrIGluIDE6bGVuZ3RoKHRpbWVfc2VxKSkgewogIEZGX3RydWVbLCBrXSA8LSBmZih0aW1lX3NlcVtrXSkgIyB0aGlzIGlzIHRoZSByaWdodCBoYW5kIHNpZGUgZnVuY3Rpb24KICBGRl9zb2xfdHJ1ZVssIGtdIDwtIHRpbWVfc2VxW2tdKkZGX3RydWVbLCBrXSAjIHRoaXMgaXMgdGhlIHNlY29uZCB0ZXJtIGluIHRoZSBzb2x1dGlvbgp9CgpVX3RydWUgPC0gVV90cnVlICsgRkZfc29sX3RydWUKCmdyYXBoX3RvX2FwcHJveF9pbnQgPC0gZ2V0c19ncmFwaF90YWRwb2xlKGggPSAwLjAwMSkKbG9jX2ZpbmVyIDwtIGdyYXBoX3RvX2FwcHJveF9pbnQkZ2V0X21lc2hfbG9jYXRpb25zKCkKQSA8LSBncmFwaCRmZW1fYmFzaXMobG9jX2ZpbmVyKQpncmFwaF90b19hcHByb3hfaW50JGNvbXB1dGVfZmVtKCkKQ19maW5lciA8LSBncmFwaF90b19hcHByb3hfaW50JG1lc2gkQwpFSUdFTkZVTl9GT1JfRkYgPC0gdGFkcG9sZS5laWcoSU5ERVhbd2hhdF9laWdlbmZ1bmN0aW9uX2Zvcl9mZl0sIGdyYXBoX3RvX2FwcHJveF9pbnQpCgppZiAoUEhJX09SX1BTSVt3aGF0X2VpZ2VuZnVuY3Rpb25fZm9yX2ZmXSA9PSAicGhpIil7CiAgZWlnZW5mdW5fZm9yX2ZmIDwtIEVJR0VORlVOX0ZPUl9GRiRwaGkKfSBlbHNlIGlmIChQSElfT1JfUFNJW3doYXRfZWlnZW5mdW5jdGlvbl9mb3JfZmZdID09ICJwc2kiKXsKICBlaWdlbmZ1bl9mb3JfZmYgPC0gRUlHRU5GVU5fRk9SX0ZGJHBzaQp9CgppbnRfYmFzaXNfZWlnZW4gPC0gYXMudmVjdG9yKHQoYXMubWF0cml4KGVpZ2VuZnVuX2Zvcl9mZikpICUqJSBDX2ZpbmVyICUqJSBBKSAKQ09FRiA8LSBjX2sqZXhwKC10aW1lX3NlcSpFSUdFTlZBTF9BTFBIQVt3aGF0X2VpZ2VuZnVuY3Rpb25fZm9yX2ZmXSkKRkZfYXBwcm94IDwtIGludF9iYXNpc19laWdlbiAlKiUgdChDT0VGKQpgYGAKCgoKCiMgU29sdmluZyBpdAoKCmBgYAp7cn0KY29hcnNlX2ggPC0gMC4xCmNvYXJzZV9ncmFwaCA8LSBnZXRzX2dyYXBoX3RhZHBvbGUoaCA9IGNvYXJzZV9oKQpjb2Fyc2VfQSA8LSBjb2Fyc2VfZ3JhcGgkZmVtX2Jhc2lzKGdyYXBoJGdldF9tZXNoX2xvY2F0aW9ucygpKQpjb2Fyc2VfdGltZV9zdGVwIDwtIDAuMQpjb2Fyc2VfdGltZV9zZXEgPC0gc2VxKDAsIFRfZmluYWwsIGJ5ID0gY29hcnNlX3RpbWVfc3RlcCkKIyBDb21wdXRlIHRoZSBGRU0gbWF0cmljZXMKY29hcnNlX2dyYXBoJGNvbXB1dGVfZmVtKCkKY29hcnNlX0cgPC0gY29hcnNlX2dyYXBoJG1lc2gkRwpjb2Fyc2VfQyA8LSBjb2Fyc2VfZ3JhcGgkbWVzaCRDCmNvYXJzZV9JIDwtIE1hdHJpeDo6RGlhZ29uYWwobnJvdyhjb2Fyc2VfQykpCmNvYXJzZV94IDwtIGNvYXJzZV9ncmFwaCRtZXNoJFZbLCAxXQpjb2Fyc2VfeSA8LSBjb2Fyc2VfZ3JhcGgkbWVzaCRWWywgMl0KY29hcnNlX2VkZ2VfbnVtYmVyIDwtIGNvYXJzZV9ncmFwaCRtZXNoJFZ0RVssIDFdCmNvYXJzZV9wb3MgPC0gc3VtKGNvYXJzZV9lZGdlX251bWJlciA9PSAxKSsxCmNvYXJzZV9vcmRlcl90b19wbG90IDwtIGZ1bmN0aW9uKHYpcmV0dXJuKGModlsxXSwgdlszOmNvYXJzZV9wb3NdLCB2WzJdLCB2Wyhjb2Fyc2VfcG9zKzEpOmxlbmd0aCh2KV0sIHZbMl0pKQoKY29hcnNlX3dlaWdodHMgPC0gY29hcnNlX2dyYXBoJG1lc2gkd2VpZ2h0cwoKY29hcnNlX0wgPC0ga2FwcGFeMipjb2Fyc2VfQyArIGNvYXJzZV9HCgpjb2Fyc2VfVV8wIDwtIHNvbHZlKHQoY29hcnNlX0EpICUqJSBjb2Fyc2VfQSwgdChjb2Fyc2VfQSkgJSolIFVfMCkKCmNvYXJzZV94IDwtIGNvYXJzZV9vcmRlcl90b19wbG90KGNvYXJzZV94KQpjb2Fyc2VfeSA8LSBjb2Fyc2Vfb3JkZXJfdG9fcGxvdChjb2Fyc2VfeSkKCnBsb3RfbHkoeCA9IH5vcmRlcl90b19wbG90KHgpLCB5ID0gfm9yZGVyX3RvX3Bsb3QoeSksIHogPSB+YXBwbHkoVV8wLCAyLG9yZGVyX3RvX3Bsb3QpWywxXSwgdHlwZSA9ICdzY2F0dGVyM2QnLCBtb2RlID0gJ2xpbmVzJykKcGxvdF9seSh4ID0gfmNvYXJzZV94LCB5ID0gfmNvYXJzZV95LCB6ID0gfmFwcGx5KGNvYXJzZV9VXzAsIDIsIGNvYXJzZV9vcmRlcl90b19wbG90KVssMV0sIHR5cGUgPSAnc2NhdHRlcjNkJywgbW9kZSA9ICdsaW5lcycpCmBgYAoKCgoKCgoKYGBgCntyfQpjb2Fyc2VfbXlfb3BfZnJhYyA8LSBteS5mcmFjdGlvbmFsLm9wZXJhdG9ycy5mcmFjKGNvYXJzZV9MLCBiZXRhLCBjb2Fyc2VfQywgc2NhbGUuZmFjdG9yID0ga2FwcGFeMiwgbSA9IG0sIGNvYXJzZV90aW1lX3N0ZXApCgpVX2FwcHJveDIgPC0gbWF0cml4KE5BLCBucm93ID0gbnJvdyhjb2Fyc2VfQyksIG5jb2wgPSBsZW5ndGgoY29hcnNlX3RpbWVfc2VxKSkKVV9hcHByb3gyWywgMV0gPC0gVV8wCgojIFRpbWUtc3RlcHBpbmcgbG9vcApmb3IgKGsgaW4gMToobGVuZ3RoKGNvYXJzZV90aW1lX3NlcSkgLSAxKSkgewogIFVfYXBwcm94MlssIGsgKyAxXSA8LSBhcy5tYXRyaXgobXkuc29sdmVyLmZyYWMoY29hcnNlX215X29wX2ZyYWMsIGNvYXJzZV9teV9vcF9mcmFjJEMgJSolIFVfYXBwcm94MlssIGtdICsgY29hcnNlX3RpbWVfc3RlcCAqIEZGX2FwcHJveFssIGsgKyAxXSkpCn0KYGBgCgpgYGB7cn0KbXlfb3BfZnJhYyA8LSBteS5mcmFjdGlvbmFsLm9wZXJhdG9ycy5mcmFjKEwsIGJldGEsIEMsIHNjYWxlLmZhY3RvciA9IGthcHBhXjIsIG0gPSBtLCB0aW1lX3N0ZXApCgpVX2FwcHJveDIgPC0gbWF0cml4KE5BLCBucm93ID0gbnJvdyhDKSwgbmNvbCA9IGxlbmd0aCh0aW1lX3NlcSkpClVfYXBwcm94MlssIDFdIDwtIFVfMAoKIyBUaW1lLXN0ZXBwaW5nIGxvb3AKZm9yIChrIGluIDE6KGxlbmd0aCh0aW1lX3NlcSkgLSAxKSkgewogIFVfYXBwcm94MlssIGsgKyAxXSA8LSBhcy5tYXRyaXgobXkuc29sdmVyLmZyYWMobXlfb3BfZnJhYywgbXlfb3BfZnJhYyRDICUqJSBVX2FwcHJveDJbLCBrXSArIHRpbWVfc3RlcCAqIEZGX2FwcHJveFssIGsgKyAxXSkpCn0KYGBgCgojIFBsb3QKCgpgYGB7cn0KI1VfYXBwcm94MiA8LSBVX2FwcHJveDEKVV9hcHByb3gxIDwtIFVfYXBwcm94MgoKCm1lYW5fdyA8LSBmdW5jdGlvbih2KXtyZXR1cm4obWVhbih2KndlaWdodHMpKX0KCm1heF9lcnJvcl9hdF9lYWNoX3RpbWUxIDwtIGFwcGx5KChVX3RydWUgLSBVX2FwcHJveDEpXjIsIDIsIG1lYW4pCm1heF9lcnJvcl9hdF9lYWNoX3RpbWUyIDwtIGFwcGx5KChVX3RydWUgLSBVX2FwcHJveDIpXjIsIDIsIG1lYW4pCm1heF9lcnJvcl9iZXR3ZWVuX2JvdGhfYXBwcm94IDwtIGFwcGx5KChVX2FwcHJveDEgLSBVX2FwcHJveDIpXjIsIDIsIG1lYW4pCgplcnJvcjEgPC0gc3FydCh0aW1lX3N0ZXAgKiBzdW0obWF4X2Vycm9yX2F0X2VhY2hfdGltZTEpKQplcnJvcjIgPC0gc3FydCh0aW1lX3N0ZXAgKiBzdW0obWF4X2Vycm9yX2F0X2VhY2hfdGltZTIpKQplcnJvcmIgPC0gc3FydCh0aW1lX3N0ZXAgKiBzdW0obWF4X2Vycm9yX2JldHdlZW5fYm90aF9hcHByb3gpKQoKCnggPC0gb3JkZXJfdG9fcGxvdCh4LCBncmFwaCkKeSA8LSBvcmRlcl90b19wbG90KHksIGdyYXBoKQpVX3RydWUgPC0gYXBwbHkoVV90cnVlLCAyLCBvcmRlcl90b19wbG90LCBncmFwaCA9IGdyYXBoKQpVX2FwcHJveDEgPC0gYXBwbHkoVV9hcHByb3gxLCAyLCBvcmRlcl90b19wbG90LCBncmFwaCA9IGdyYXBoKQpVX2FwcHJveDIgPC0gYXBwbHkoVV9hcHByb3gyLCAyLCBvcmRlcl90b19wbG90LCBncmFwaCA9IGdyYXBoKQoKIyBDcmVhdGUgaW50ZXJhY3RpdmUgcGxvdApmaWcgPC0gcGxvdF9seSgpCgojIEFkZCBmaXJzdCBsaW5lIChtYXhfZXJyb3JfYXRfZWFjaF90aW1lMSkKZmlnIDwtIGZpZyAlPiUgYWRkX3RyYWNlKAogIHggPSB+dGltZV9zZXEsIHkgPSB+bWF4X2Vycm9yX2F0X2VhY2hfdGltZTEsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsCiAgbGluZSA9IGxpc3QoY29sb3IgPSAncmVkJywgd2lkdGggPSAyKSwKICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJ3JlZCcsIHNpemUgPSA0KSwKICBuYW1lID0gcGFzdGUwKCJFcnJvciAgVHJ1ZSBhbmQgQXBwcm94IDE6ICIsIHNwcmludGYoIiUuM2UiLCBlcnJvcjEpKQopCgojIEFkZCBzZWNvbmQgbGluZSAobWF4X2Vycm9yX2F0X2VhY2hfdGltZTIpCmZpZyA8LSBmaWcgJT4lIGFkZF90cmFjZSgKICB4ID0gfnRpbWVfc2VxLCB5ID0gfm1heF9lcnJvcl9hdF9lYWNoX3RpbWUyLCB0eXBlID0gJ3NjYXR0ZXInLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLAogIGxpbmUgPSBsaXN0KGNvbG9yID0gJ2JsdWUnLCB3aWR0aCA9IDIsIGRhc2ggPSAiZG90IiksCiAgbWFya2VyID0gbGlzdChjb2xvciA9ICdibHVlJywgc2l6ZSA9IDQpLAogIG5hbWUgPSBwYXN0ZTAoIkVycm9yICBUcnVlIGFuZCBBcHByb3ggMjogIiwgc3ByaW50ZigiJS4zZSIsIGVycm9yMikpCikKCiMgQWRkIHRoaXJkIGxpbmUgKG1heF9lcnJvcl9iZXR3ZWVuX2JvdGhfYXBwcm94KQoKZmlnIDwtIGZpZyAlPiUgYWRkX3RyYWNlKAogIHggPSB+dGltZV9zZXEsIHkgPSB+bWF4X2Vycm9yX2JldHdlZW5fYm90aF9hcHByb3gsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsCiAgbGluZSA9IGxpc3QoY29sb3IgPSAnb3JhbmdlJywgd2lkdGggPSAyKSwKICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJ29yYW5nZScsIHNpemUgPSA0KSwKICBuYW1lID0gcGFzdGUwKCJFcnJvciBCZXR3ZWVuIEFwcHJveGltYXRpb25zOiAiLCBzcHJpbnRmKCIlLjNlIiwgZXJyb3JiKSkKKQoKIyBMYXlvdXQKZmlnIDwtIGZpZyAlPiUgbGF5b3V0KAogIHRpdGxlID0gIkVycm9yIGF0IEVhY2ggVGltZSBTdGVwIiwKICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiVGltZSIpLAogIHlheGlzID0gbGlzdCh0aXRsZSA9ICJFcnJvciIpLAogIGxlZ2VuZCA9IGxpc3QoeCA9IDAuMSwgeSA9IDAuOSkKKQoKCnBsb3RfZGF0YSA8LSBkYXRhLmZyYW1lKAogIHggPSByZXAoeCwgdGltZXMgPSBuY29sKFVfdHJ1ZSkpLAogIHkgPSByZXAoeSwgdGltZXMgPSBuY29sKFVfdHJ1ZSkpLAogIHpfdHJ1ZSA9IGFzLnZlY3RvcihVX3RydWUpLAogIHpfYXBwcm94MSA9IGFzLnZlY3RvcihVX2FwcHJveDEpLAogIHpfYXBwcm94MiA9IGFzLnZlY3RvcihVX2FwcHJveDIpLAogIGZyYW1lID0gcmVwKHRpbWVfc2VxLCBlYWNoID0gbGVuZ3RoKHgpKQopCgojIENvbXB1dGUgYXhpcyBsaW1pdHMKeF9yYW5nZSA8LSByYW5nZSh4KQp5X3JhbmdlIDwtIHJhbmdlKHkpCnpfcmFuZ2UgPC0gcmFuZ2UoYyhVX3RydWUsIFVfYXBwcm94MSwgVV9hcHByb3gyKSkKCiMgSW5pdGlhbCBwbG90IHNldHVwIChmaXJzdCBmcmFtZSBvbmx5KQpwIDwtIHBsb3RfbHkocGxvdF9kYXRhLCBmcmFtZSA9IH5mcmFtZSkgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IH54LCB5ID0gfnksIHogPSB+el90cnVlLAogICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgIG5hbWUgPSAiVHJ1ZSIsCiAgICBsaW5lID0gbGlzdChjb2xvciA9ICJncmVlbiIsIHdpZHRoID0gMikKICApICU+JQogIGFkZF90cmFjZSgKICAgIHggPSB+eCwgeSA9IH55LCB6ID0gfnpfYXBwcm94MSwKICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICBuYW1lID0gIkFwcHJveCAxIiwKICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIHdpZHRoID0gMikKICApICU+JQogIGFkZF90cmFjZSgKICAgIHggPSB+eCwgeSA9IH55LCB6ID0gfnpfYXBwcm94MiwKICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICBuYW1lID0gIkFwcHJveCAyIiwKICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCB3aWR0aCA9IDIpCiAgKSAlPiUKICBsYXlvdXQoCiAgICBzY2VuZSA9IGxpc3QoCiAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJ4IiwgcmFuZ2UgPSB4X3JhbmdlKSwKICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gInkiLCByYW5nZSA9IHlfcmFuZ2UpLAogICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAiVmFsdWUiLCByYW5nZSA9IHpfcmFuZ2UpLAogICAgICBhc3BlY3RyYXRpbyA9IGxpc3QoeCA9IDIuNCwgeSA9IDEuMiwgeiA9IDEuMiksCiAgICAgICAgICAgY2FtZXJhID0gbGlzdCgKICAgICAgZXllID0gbGlzdCh4ID0gMS41LCB5ID0gMS41LCB6ID0gMSksICAjIEFkanVzdCB0aGUgdmlld3BvaW50CiAgICAgIGNlbnRlciA9IGxpc3QoeCA9IDAsIHkgPSAwLCB6ID0gMCkpCiAgICApLAogICAgdXBkYXRlbWVudXMgPSBsaXN0KAogICAgICBsaXN0KAogICAgICAgIHR5cGUgPSAiYnV0dG9ucyIsIHNob3dhY3RpdmUgPSBGQUxTRSwKICAgICAgICBidXR0b25zID0gbGlzdCgKICAgICAgICAgIGxpc3QobGFiZWwgPSAiUGxheSIsIG1ldGhvZCA9ICJhbmltYXRlIiwKICAgICAgICAgICAgICAgYXJncyA9IGxpc3QoTlVMTCwgbGlzdChmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAxMDAsIHJlZHJhdyA9IFRSVUUpLCBmcm9tY3VycmVudCA9IFRSVUUpKSksCiAgICAgICAgICBsaXN0KGxhYmVsID0gIlBhdXNlIiwgbWV0aG9kID0gImFuaW1hdGUiLAogICAgICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLCBsaXN0KG1vZGUgPSAiaW1tZWRpYXRlIiwgZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMCksIHJlZHJhdyA9IEZBTFNFKSkpCiAgICAgICAgKQogICAgICApCiAgICApLAogICAgdGl0bGUgPSAiVGltZTogMCIKICApCgojIENvbnZlcnQgdG8gcGxvdGx5IG9iamVjdCB3aXRoIGZyYW1lIGluZm8KcGIgPC0gcGxvdGx5X2J1aWxkKHApCgojIEluamVjdCBjdXN0b20gdGl0bGVzIGludG8gZWFjaCBmcmFtZQpmb3IgKGkgaW4gc2VxX2Fsb25nKHBiJHgkZnJhbWVzKSkgewogIHQgPC0gdGltZV9zZXFbaV0KICBlcnIgPC0gc2lnbmlmKG1heF9lcnJvcl9iZXR3ZWVuX2JvdGhfYXBwcm94W2ldLCA0KQogIHBiJHgkZnJhbWVzW1tpXV0kbGF5b3V0IDwtIGxpc3QodGl0bGUgPSBwYXN0ZTAoIlRpbWU6ICIsIHQsICIgfCBFcnJvcjogIiwgZXJyKSkKfQpgYGAKCgpgYGB7cn0KIyMgVGhpcyBpcyB0byBwbG90IHRoZSByaWdodCBoYW5kIHNpZGUgYWxvbmUKCkZGX3RydWUgPC0gYXBwbHkoRkZfdHJ1ZSwgMiwgb3JkZXJfdG9fcGxvdCwgZ3JhcGggPSBncmFwaCkKRkZfc29sX3RydWUgPC0gYXBwbHkoRkZfc29sX3RydWUsIDIsIG9yZGVyX3RvX3Bsb3QsIGdyYXBoID0gZ3JhcGgpCkZGX2FwcHJveCA8LSBhcHBseShGRl9hcHByb3gsIDIsIG9yZGVyX3RvX3Bsb3QsIGdyYXBoID0gZ3JhcGgpCgpwbG90X2RhdGEgPC0gZGF0YS5mcmFtZSgKICB4ID0gcmVwKHgsIHRpbWVzID0gbmNvbChGRl90cnVlKSksCiAgeSA9IHJlcCh5LCB0aW1lcyA9IG5jb2woRkZfdHJ1ZSkpLAogIGZmX3RydWUgPSBhcy52ZWN0b3IoRkZfdHJ1ZSksCiAgZmZfc29sX3RydWUgPSBhcy52ZWN0b3IoRkZfc29sX3RydWUpLAogIGZmX2FwcHJveCA9IGFzLnZlY3RvcihGRl9hcHByb3gpLAogIGZyYW1lID0gcmVwKHRpbWVfc2VxLCBlYWNoID0gbGVuZ3RoKHgpKQopCgojIENvbXB1dGUgYXhpcyBsaW1pdHMKeF9yYW5nZSA8LSByYW5nZSh4KQp5X3JhbmdlIDwtIHJhbmdlKHkpCnpfcmFuZ2UgPC0gcmFuZ2UoYyhGRl90cnVlLCBGRl9zb2xfdHJ1ZSwgRkZfYXBwcm94KSkKCiMgSW5pdGlhbCBwbG90IHNldHVwIChmaXJzdCBmcmFtZSBvbmx5KQpwX2ZmIDwtIHBsb3RfbHkocGxvdF9kYXRhLCBmcmFtZSA9IH5mcmFtZSkgJT4lCiAgYWRkX3RyYWNlKAogICAgeCA9IH54LCB5ID0gfnksIHogPSB+ZmZfdHJ1ZSwKICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgbW9kZSA9ICJsaW5lcyIsCiAgICBuYW1lID0gImYocyx0KSIsCiAgICBsaW5lID0gbGlzdChjb2xvciA9ICJncmVlbiIsIHdpZHRoID0gMikKICApICU+JQogIGFkZF90cmFjZSgKICAgIHggPSB+eCwgeSA9IH55LCB6ID0gfmZmX3NvbF90cnVlLAogICAgdHlwZSA9ICJzY2F0dGVyM2QiLCBtb2RlID0gImxpbmVzIiwKICAgIG5hbWUgPSAidGYocyx0KSA9IHUocyx0KS1TT0wodV8wKSIsCiAgICBsaW5lID0gbGlzdChjb2xvciA9ICJyZWQiLCB3aWR0aCA9IDIpCiAgKSAlPiUKICBhZGRfdHJhY2UoCiAgICB4ID0gfngsIHkgPSB+eSwgeiA9IH5mZl9hcHByb3gsCiAgICB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGUgPSAibGluZXMiLAogICAgbmFtZSA9ICJGXmsgPSAoZl5rLCBwaGkpIiwKICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsdWUiLCB3aWR0aCA9IDIpCiAgKSAlPiUKICBsYXlvdXQoCiAgICBzY2VuZSA9IGxpc3QoCiAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJ4IiwgcmFuZ2UgPSB4X3JhbmdlKSwKICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gInkiLCByYW5nZSA9IHlfcmFuZ2UpLAogICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAiVmFsdWUiLCByYW5nZSA9IHpfcmFuZ2UpLAogICAgICBhc3BlY3RyYXRpbyA9IGxpc3QoeCA9IDIuNCwgeSA9IDEuMiwgeiA9IDEuMiksCiAgICAgICAgICAgY2FtZXJhID0gbGlzdCgKICAgICAgZXllID0gbGlzdCh4ID0gMS41LCB5ID0gMS41LCB6ID0gMSksICAjIEFkanVzdCB0aGUgdmlld3BvaW50CiAgICAgIGNlbnRlciA9IGxpc3QoeCA9IDAsIHkgPSAwLCB6ID0gMCkpCiAgICApLAogICAgdXBkYXRlbWVudXMgPSBsaXN0KAogICAgICBsaXN0KAogICAgICAgIHR5cGUgPSAiYnV0dG9ucyIsIHNob3dhY3RpdmUgPSBGQUxTRSwKICAgICAgICBidXR0b25zID0gbGlzdCgKICAgICAgICAgIGxpc3QobGFiZWwgPSAiUGxheSIsIG1ldGhvZCA9ICJhbmltYXRlIiwKICAgICAgICAgICAgICAgYXJncyA9IGxpc3QoTlVMTCwgbGlzdChmcmFtZSA9IGxpc3QoZHVyYXRpb24gPSAxMDAsIHJlZHJhdyA9IFRSVUUpLCBmcm9tY3VycmVudCA9IFRSVUUpKSksCiAgICAgICAgICBsaXN0KGxhYmVsID0gIlBhdXNlIiwgbWV0aG9kID0gImFuaW1hdGUiLAogICAgICAgICAgICAgICBhcmdzID0gbGlzdChOVUxMLCBsaXN0KG1vZGUgPSAiaW1tZWRpYXRlIiwgZnJhbWUgPSBsaXN0KGR1cmF0aW9uID0gMCksIHJlZHJhdyA9IEZBTFNFKSkpCiAgICAgICAgKQogICAgICApCiAgICApLAogICAgdGl0bGUgPSAiVGltZTogMCIKICApCgojIENvbnZlcnQgdG8gcGxvdGx5IG9iamVjdCB3aXRoIGZyYW1lIGluZm8KcGJfZmYgPC0gcGxvdGx5X2J1aWxkKHBfZmYpCgojIEluamVjdCBjdXN0b20gdGl0bGVzIGludG8gZWFjaCBmcmFtZQpmb3IgKGkgaW4gc2VxX2Fsb25nKHBiX2ZmJHgkZnJhbWVzKSkgewogIHQgPC0gdGltZV9zZXFbaV0KICBwYl9mZiR4JGZyYW1lc1tbaV1dJGxheW91dCA8LSBsaXN0KHRpdGxlID0gcGFzdGUwKCJUaW1lOiAiLCB0KSkKfQpgYGAKCmBgYHtyfQpmaWcgICMgRGlzcGxheSB0aGUgcGxvdApgYGAKCgpgYGB7ciwgZmlnLmhlaWdodCA9IDYsIG91dC53aWR0aCA9ICIxMDAlIiwgZmlnLmNhcCA9IGNhcHRpb25lcigiQ2FwdGlvbiIpfQpwYgpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodCA9IDYsIG91dC53aWR0aCA9ICIxMDAlIiwgZmlnLmNhcCA9IGNhcHRpb25lcigiQ2FwdGlvbiIpfQpwYl9mZgpgYGA=